package cn.shiwei.hr.service.impl;

import cn.shiwei.hr.domain.KillCourse;
import cn.shiwei.hr.dto.CourseKillDto;
import cn.shiwei.hr.mapper.KillCourseMapper;
import cn.shiwei.hr.repository.AssertUtil;
import cn.shiwei.hr.service.IKillCourseService;
import cn.shiwei.hr.to.TempOrderTo;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.rabbitmq.client.AMQP;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.security.KeyStore;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import static cn.shiwei.hr.constants.KillCourseConstants.*;
import static cn.shiwei.hr.constants.MessageConstants.EXCHANGE_KILL_TOPIC;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 1363732197@qq.com
 * @since 2021-12-10
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private IKillCourseService killCourseService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    private long courseId = 1L;
    @Override
    public void online(KillCourse killCourse) {
        /*
        校验数据
            1. 设置的课程价格, 时间, 秒杀数量, 结束时间不能为空 @Valid
         */
        // 根据查询数据库对应的课程
        // 模拟数据
        killCourse.setCourseId(courseId);
        courseId++;
        killCourse.setCourseName("spring入门");
        killCourse.setCoursePic("");
        Date date = new Date();
        killCourse.setCreateTime(date);
        killCourse.setEndTime(DateUtils.addDays(date, 2));
        killCourse.setKillCount(100);
        killCourse.setKillPrice(new BigDecimal("120"));
        killCourse.setKillSort(1);
        killCourse.setKillStatus(KillCourse.KILL_STATUS_NOT_ONLINE);
        killCourse.setSessionNumber("1");
        killCourse.setStartTime(date);

        super.insert(killCourse);
    }


    @Override
    public void publishASpikeCourse() {
        // 定时任务中需要查询秒杀课程表中, 时间在当前时间到两天间的课程, 且课程的状态需要为待上线
        Wrapper<KillCourse> wapper = new EntityWrapper<>();
        Date now = new Date();
        // 时间范围
        // 时间判断应该是: 结束时间晚于现在时间,
        wapper.gt("end_time", now);
        // 课程状态
        wapper.eq("kill_status", KillCourse.KILL_STATUS_NOT_ONLINE);
        List<KillCourse> killCourses = killCourseService.selectList(wapper);
        if (killCourses == null || killCourses.size() == 0) {
            log.info("没有未上线的秒杀课程");
            return;
        }
        // 存入缓存, 需要生成一个随机码, 用于验证
        // 好像没有用处
        killCourses.forEach(killCourse -> {
            // 将数据存入redis, 如果存入成功, 就正常的再用信号量存入库存, 如果存入失败, 表示已经有这个数据(小k)了
            // 设置好秒杀码  用于防止脚本
            killCourse.setCode(UUID.randomUUID().toString());
            Boolean success = redisTemplate.opsForHash().putIfAbsent(CACHE_KEY, killCourse.getId().toString(), killCourse);
            if (success) {
                // 存入信号量库存
                boolean bool = redissonClient.getSemaphore(String.format(SIGNAL_KEY, killCourse.getCourseId().toString())).trySetPermits(killCourse.getKillCount());
                // 如果存入失败, 就删除缓存中的秒杀课程,  成功就, 修改秒杀课程状态未上架,
                if (bool) {
                    killCourse.setKillStatus(KillCourse.KILL_STATUS_ONLINE);
                    super.updateById(killCourse);
                    log.info("秒杀课程发布成功");
                } else {
                    redisTemplate.opsForHash().delete(CACHE_KEY, killCourse.getId().toString());
                    log.error("设置库存失败");
                }
            }
        });

    }

    @Override
    public List<?> list() {
        // 查询全部的秒杀活动,
        List<Object> values = redisTemplate.opsForHash().values(CACHE_KEY);
        values.forEach(obj -> {
            KillCourse killCourse = (KillCourse) obj;
            if (killCourse.getStartTime().after(new Date())){
                killCourse.setCode(null);
            }
        });
        return values;
    }

    @Override
    public KillCourse getOne(Long id) {
        return (KillCourse) redisTemplate.opsForHash().get(CACHE_KEY, id.toString());
    }

    @Override
    public String startSpike(CourseKillDto courseKillDto) {

        // 参数校验, 非空 @Valid
        Long id = courseKillDto.getId();
        String code = courseKillDto.getCode();

        // 用户登录校验, 测试默认登录!!!
        Long loginId = 1L;
        KillCourse killCourse  = (KillCourse) redisTemplate.opsForHash().get(CACHE_KEY, id.toString());
        // 秒杀课程校验
        AssertUtil.isNull(killCourse, "课程还未开启秒杀活动");
        // 时间校验
        AssertUtil.isTrue(killCourse.getStartTime().after(new Date()), "秒杀时间未到");
        // 秒杀码校验
        AssertUtil.isTrue(killCourse.getCode().equals(code), "非法请求");

        // 校验通过后, 减库存, 生成临时订单, 给mq发消息(延迟队列)
        // 减库存, 信号量减1, 这个 1 是模拟的用户下单数量, 秒杀活动也应该是 1
        redissonClient.getSemaphore(String.format(SIGNAL_KEY, killCourse.getCourseId().toString())).tryAcquire(1);

        // 生成临时订单, tempOrder
        String orderNo = UUID.randomUUID().toString();
        TempOrderTo tempOrderTo = new TempOrderTo(
                loginId,
                id,
                killCourse.getCourseName(),
                killCourse.getKillPrice(),
                orderNo
        );

        // 将临时的订单保存到redis
        String format = String.format(CACHE_ORDER, loginId, orderNo);
        redisTemplate.opsForValue().set(format, tempOrderTo);

        // 保存成功后, 调用rabbitmq, 做延迟队列 (为什么不使用redis的过期, 原因: redis只能干掉redis中的, 而超时会修改多处的状态)
        rabbitTemplate.convertAndSend(EXCHANGE_KILL_TOPIC,"order.delay", format);
        log.info("已向RabbitMQ发起通知");

        return orderNo;
    }


}
